{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-01-17T11:24:35.560408Z",
     "start_time": "2025-01-17T11:24:35.553543Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "execution_count": 267
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## 准备数据\n",
    "对非数据集数据进行处理，将其转换为张量。\n"
   ],
   "id": "b2ca68305d4f1003"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:24:35.635889Z",
     "start_time": "2025-01-17T11:24:35.624425Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing(data_home=\"./data\")\n",
    "\n",
    "print(type(housing))  # <class'sklearn.utils.Bunch'>\n",
    "print(\"-\" * 50)\n",
    "\n",
    "print(housing.data.shape)  # (20640, 8)\n",
    "print(\"-\" * 50)\n",
    "print(type(housing.data))  # <class 'numpy.ndarray'>\n",
    "print(\"-\" * 50)\n",
    "\n",
    "print(housing.target.shape)  # (20640,)\n",
    "print(\"-\" * 50)\n",
    "print(type(housing.target))  # <class 'numpy.ndarray'>"
   ],
   "id": "179320fc991c0d9e",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'sklearn.utils._bunch.Bunch'>\n",
      "--------------------------------------------------\n",
      "(20640, 8)\n",
      "--------------------------------------------------\n",
      "<class 'numpy.ndarray'>\n",
      "--------------------------------------------------\n",
      "(20640,)\n",
      "--------------------------------------------------\n",
      "<class 'numpy.ndarray'>\n"
     ]
    }
   ],
   "execution_count": 268
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:24:35.652297Z",
     "start_time": "2025-01-17T11:24:35.641892Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "# 将数据集分为训练集和测试集\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state=7, test_size=0.25)\n",
    "\n",
    "# 将训练集分为训练集和验证集\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state=11)\n",
    "\n",
    "# 训练集\n",
    "print(x_train.shape, y_train.shape)\n",
    "\n",
    "# 验证集\n",
    "print(x_valid.shape, y_valid.shape)\n",
    "\n",
    "# 测试集\n",
    "print(x_test.shape, y_test.shape)\n",
    "\n",
    "# 把3个数据集都放到字典中\n",
    "dataset_maps = {\n",
    "    \"train\": (x_train, y_train),  # 训练集\n",
    "    \"valid\": (x_valid, y_valid),  # 验证集\n",
    "    \"test\": (x_test, y_test)  # 测试集\n",
    "}\n"
   ],
   "id": "6438d1e42b76dee3",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(11610, 8) (11610,)\n",
      "(3870, 8) (3870,)\n",
      "(5160, 8) (5160,)\n"
     ]
    }
   ],
   "execution_count": 269
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:24:35.670070Z",
     "start_time": "2025-01-17T11:24:35.663302Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "\n",
    "scaler = StandardScaler()  #标准化\n",
    "\n",
    "# fit和fit_transform的区别，\n",
    "# fit是计算均值和方差，fit_transform是先fit，然后transform\n",
    "scaler.fit(x_train)"
   ],
   "id": "31716505cd2977d0",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StandardScaler()"
      ],
      "text/html": [
       "<style>#sk-container-id-14 {\n",
       "  /* Definition of color scheme common for light and dark mode */\n",
       "  --sklearn-color-text: #000;\n",
       "  --sklearn-color-text-muted: #666;\n",
       "  --sklearn-color-line: gray;\n",
       "  /* Definition of color scheme for unfitted estimators */\n",
       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
       "  --sklearn-color-unfitted-level-3: chocolate;\n",
       "  /* Definition of color scheme for fitted estimators */\n",
       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
       "\n",
       "  /* Specific color for light theme */\n",
       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-icon: #696969;\n",
       "\n",
       "  @media (prefers-color-scheme: dark) {\n",
       "    /* Redefinition of color scheme for dark theme */\n",
       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-icon: #878787;\n",
       "  }\n",
       "}\n",
       "\n",
       "#sk-container-id-14 {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "#sk-container-id-14 pre {\n",
       "  padding: 0;\n",
       "}\n",
       "\n",
       "#sk-container-id-14 input.sk-hidden--visually {\n",
       "  border: 0;\n",
       "  clip: rect(1px 1px 1px 1px);\n",
       "  clip: rect(1px, 1px, 1px, 1px);\n",
       "  height: 1px;\n",
       "  margin: -1px;\n",
       "  overflow: hidden;\n",
       "  padding: 0;\n",
       "  position: absolute;\n",
       "  width: 1px;\n",
       "}\n",
       "\n",
       "#sk-container-id-14 div.sk-dashed-wrapped {\n",
       "  border: 1px dashed var(--sklearn-color-line);\n",
       "  margin: 0 0.4em 0.5em 0.4em;\n",
       "  box-sizing: border-box;\n",
       "  padding-bottom: 0.4em;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "#sk-container-id-14 div.sk-container {\n",
       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
       "     so we also need the `!important` here to be able to override the\n",
       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
       "  display: inline-block !important;\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-14 div.sk-text-repr-fallback {\n",
       "  display: none;\n",
       "}\n",
       "\n",
       "div.sk-parallel-item,\n",
       "div.sk-serial,\n",
       "div.sk-item {\n",
       "  /* draw centered vertical line to link estimators */\n",
       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
       "  background-size: 2px 100%;\n",
       "  background-repeat: no-repeat;\n",
       "  background-position: center center;\n",
       "}\n",
       "\n",
       "/* Parallel-specific style estimator block */\n",
       "\n",
       "#sk-container-id-14 div.sk-parallel-item::after {\n",
       "  content: \"\";\n",
       "  width: 100%;\n",
       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
       "  flex-grow: 1;\n",
       "}\n",
       "\n",
       "#sk-container-id-14 div.sk-parallel {\n",
       "  display: flex;\n",
       "  align-items: stretch;\n",
       "  justify-content: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-14 div.sk-parallel-item {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "}\n",
       "\n",
       "#sk-container-id-14 div.sk-parallel-item:first-child::after {\n",
       "  align-self: flex-end;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-14 div.sk-parallel-item:last-child::after {\n",
       "  align-self: flex-start;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-14 div.sk-parallel-item:only-child::after {\n",
       "  width: 0;\n",
       "}\n",
       "\n",
       "/* Serial-specific style estimator block */\n",
       "\n",
       "#sk-container-id-14 div.sk-serial {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "  align-items: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  padding-right: 1em;\n",
       "  padding-left: 1em;\n",
       "}\n",
       "\n",
       "\n",
       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
       "clickable and can be expanded/collapsed.\n",
       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
       "*/\n",
       "\n",
       "/* Pipeline and ColumnTransformer style (default) */\n",
       "\n",
       "#sk-container-id-14 div.sk-toggleable {\n",
       "  /* Default theme specific background. It is overwritten whether we have a\n",
       "  specific estimator or a Pipeline/ColumnTransformer */\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "/* Toggleable label */\n",
       "#sk-container-id-14 label.sk-toggleable__label {\n",
       "  cursor: pointer;\n",
       "  display: flex;\n",
       "  width: 100%;\n",
       "  margin-bottom: 0;\n",
       "  padding: 0.5em;\n",
       "  box-sizing: border-box;\n",
       "  text-align: center;\n",
       "  align-items: start;\n",
       "  justify-content: space-between;\n",
       "  gap: 0.5em;\n",
       "}\n",
       "\n",
       "#sk-container-id-14 label.sk-toggleable__label .caption {\n",
       "  font-size: 0.6rem;\n",
       "  font-weight: lighter;\n",
       "  color: var(--sklearn-color-text-muted);\n",
       "}\n",
       "\n",
       "#sk-container-id-14 label.sk-toggleable__label-arrow:before {\n",
       "  /* Arrow on the left of the label */\n",
       "  content: \"▸\";\n",
       "  float: left;\n",
       "  margin-right: 0.25em;\n",
       "  color: var(--sklearn-color-icon);\n",
       "}\n",
       "\n",
       "#sk-container-id-14 label.sk-toggleable__label-arrow:hover:before {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "/* Toggleable content - dropdown */\n",
       "\n",
       "#sk-container-id-14 div.sk-toggleable__content {\n",
       "  max-height: 0;\n",
       "  max-width: 0;\n",
       "  overflow: hidden;\n",
       "  text-align: left;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-14 div.sk-toggleable__content.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-14 div.sk-toggleable__content pre {\n",
       "  margin: 0.2em;\n",
       "  border-radius: 0.25em;\n",
       "  color: var(--sklearn-color-text);\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-14 div.sk-toggleable__content.fitted pre {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-14 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
       "  /* Expand drop-down */\n",
       "  max-height: 200px;\n",
       "  max-width: 100%;\n",
       "  overflow: auto;\n",
       "}\n",
       "\n",
       "#sk-container-id-14 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
       "  content: \"▾\";\n",
       "}\n",
       "\n",
       "/* Pipeline/ColumnTransformer-specific style */\n",
       "\n",
       "#sk-container-id-14 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-14 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator-specific style */\n",
       "\n",
       "/* Colorize estimator box */\n",
       "#sk-container-id-14 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-14 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-14 div.sk-label label.sk-toggleable__label,\n",
       "#sk-container-id-14 div.sk-label label {\n",
       "  /* The background is the default theme color */\n",
       "  color: var(--sklearn-color-text-on-default-background);\n",
       "}\n",
       "\n",
       "/* On hover, darken the color of the background */\n",
       "#sk-container-id-14 div.sk-label:hover label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "/* Label box, darken color on hover, fitted */\n",
       "#sk-container-id-14 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator label */\n",
       "\n",
       "#sk-container-id-14 div.sk-label label {\n",
       "  font-family: monospace;\n",
       "  font-weight: bold;\n",
       "  display: inline-block;\n",
       "  line-height: 1.2em;\n",
       "}\n",
       "\n",
       "#sk-container-id-14 div.sk-label-container {\n",
       "  text-align: center;\n",
       "}\n",
       "\n",
       "/* Estimator-specific */\n",
       "#sk-container-id-14 div.sk-estimator {\n",
       "  font-family: monospace;\n",
       "  border: 1px dotted var(--sklearn-color-border-box);\n",
       "  border-radius: 0.25em;\n",
       "  box-sizing: border-box;\n",
       "  margin-bottom: 0.5em;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-14 div.sk-estimator.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "/* on hover */\n",
       "#sk-container-id-14 div.sk-estimator:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-14 div.sk-estimator.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
       "\n",
       "/* Common style for \"i\" and \"?\" */\n",
       "\n",
       ".sk-estimator-doc-link,\n",
       "a:link.sk-estimator-doc-link,\n",
       "a:visited.sk-estimator-doc-link {\n",
       "  float: right;\n",
       "  font-size: smaller;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1em;\n",
       "  height: 1em;\n",
       "  width: 1em;\n",
       "  text-decoration: none !important;\n",
       "  margin-left: 0.5em;\n",
       "  text-align: center;\n",
       "  /* unfitted */\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted,\n",
       "a:link.sk-estimator-doc-link.fitted,\n",
       "a:visited.sk-estimator-doc-link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "/* Span, style for the box shown on hovering the info icon */\n",
       ".sk-estimator-doc-link span {\n",
       "  display: none;\n",
       "  z-index: 9999;\n",
       "  position: relative;\n",
       "  font-weight: normal;\n",
       "  right: .2ex;\n",
       "  padding: .5ex;\n",
       "  margin: .5ex;\n",
       "  width: min-content;\n",
       "  min-width: 20ex;\n",
       "  max-width: 50ex;\n",
       "  color: var(--sklearn-color-text);\n",
       "  box-shadow: 2pt 2pt 4pt #999;\n",
       "  /* unfitted */\n",
       "  background: var(--sklearn-color-unfitted-level-0);\n",
       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted span {\n",
       "  /* fitted */\n",
       "  background: var(--sklearn-color-fitted-level-0);\n",
       "  border: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link:hover span {\n",
       "  display: block;\n",
       "}\n",
       "\n",
       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
       "\n",
       "#sk-container-id-14 a.estimator_doc_link {\n",
       "  float: right;\n",
       "  font-size: 1rem;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1rem;\n",
       "  height: 1rem;\n",
       "  width: 1rem;\n",
       "  text-decoration: none;\n",
       "  /* unfitted */\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "}\n",
       "\n",
       "#sk-container-id-14 a.estimator_doc_link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "#sk-container-id-14 a.estimator_doc_link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "#sk-container-id-14 a.estimator_doc_link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "</style><div id=\"sk-container-id-14\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>StandardScaler()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-14\" type=\"checkbox\" checked><label for=\"sk-estimator-id-14\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow\"><div><div>StandardScaler</div></div><div><a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.6/modules/generated/sklearn.preprocessing.StandardScaler.html\">?<span>Documentation for StandardScaler</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></div></label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler()</pre></div> </div></div></div></div>"
      ]
     },
     "execution_count": 270,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 270
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 构建数据集",
   "id": "16087ce2514248b7"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:24:35.719378Z",
     "start_time": "2025-01-17T11:24:35.711083Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#构建私有数据集，就需要用如下方式\n",
    "from torch.utils.data import Dataset\n",
    "\n",
    "\n",
    "# 因为最终目的是为了将数据分批，所以要继承自Dataset\n",
    "class HousingDataset(Dataset):\n",
    "    def __init__(self, dataset_maps, mode=\"train\"):\n",
    "        # x,y都是ndarray类型\n",
    "        self.x, self.y = dataset_maps[mode]\n",
    "        # from_numpy将NumPy数组转换成PyTorch张量\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float()\n",
    "        # 处理为多行1列的tensor类型,因为__getitem__切片时需要\n",
    "        self.y = torch.from_numpy(self.y.reshape(-1, 1)).float()\n",
    "        # 处理为多行1列的tensor类型,因为__getitem__切片时需要\n",
    "\n",
    "    def __len__(self):  # 必须写\n",
    "        return len(self.x)  # 返回数据集的长度，0维的size\n",
    "\n",
    "    def __getitem__(self, index):  # 必须写\n",
    "        # idx是索引，返回的是数据和标签，这里是一个样本\n",
    "        return self.x[index], self.y[index]\n",
    "        # 返回第index个数据，0维的index\n",
    "\n",
    "\n",
    "#train_ds是dataset类型的数据，与前面例子的FashionMNIST类型一致\n",
    "train_ds = HousingDataset(dataset_maps, mode=\"train\")\n",
    "valid_ds = HousingDataset(dataset_maps, mode=\"valid\")\n",
    "test_ds = HousingDataset(dataset_maps, mode=\"test\")"
   ],
   "id": "6983b17b0a43ddbf",
   "outputs": [],
   "execution_count": 271
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:24:35.727455Z",
     "start_time": "2025-01-17T11:24:35.721382Z"
    }
   },
   "cell_type": "code",
   "source": "train_ds[0]  # 第一个样本的x和y，都是张量",
   "id": "32eb5ab0d2d15cec",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([ 0.8015,  0.2722, -0.1162, -0.2023, -0.5431, -0.0210, -0.5898, -0.0824]),\n",
       " tensor([3.2260]))"
      ]
     },
     "execution_count": 272,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 272
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:24:35.740740Z",
     "start_time": "2025-01-17T11:24:35.734459Z"
    }
   },
   "cell_type": "code",
   "source": "train_ds[0][0]",
   "id": "5b70d99fe2daee9e",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([ 0.8015,  0.2722, -0.1162, -0.2023, -0.5431, -0.0210, -0.5898, -0.0824])"
      ]
     },
     "execution_count": 273,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 273
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:24:35.757953Z",
     "start_time": "2025-01-17T11:24:35.751748Z"
    }
   },
   "cell_type": "code",
   "source": "train_ds[0][1]",
   "id": "2cac8b93a241b2cc",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([3.2260])"
      ]
     },
     "execution_count": 274,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 274
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## DataLoader",
   "id": "ac48caac2c0b7d1b"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:24:35.780388Z",
     "start_time": "2025-01-17T11:24:35.775960Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "# 放到DataLoader中的train_ds, valid_ds, test_ds都是dataset类型的数据\n",
    "# 过大会导致GPU内存溢出，过小会导致训练时间过长\n",
    "batch_size = 8  #batch_size是可以调的超参数\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ],
   "id": "e4913bc7597b3d04",
   "outputs": [],
   "execution_count": 275
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 定义模型",
   "id": "d35e08caf2939a19"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:24:35.830953Z",
     "start_time": "2025-01-17T11:24:35.824400Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#回归模型我们只需要1个数\n",
    "\n",
    "class WideDeep(nn.Module):\n",
    "    def __init__(self, input_dim=8):\n",
    "        super().__init__()\n",
    "        # 全连接层\n",
    "        # Deep 部分：用于学习输入特征的高阶非线性关系。\n",
    "        self.deep = nn.Sequential(\n",
    "            nn.Linear(input_dim, 30),  # 30个神经元\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(30, 30),  # 30个神经元\n",
    "            nn.ReLU()\n",
    "            )\n",
    "        # 输出层\n",
    "        # 将 Deep 部分的输出（30 维）和原始输入特征（input_dim 维）拼接后，映射到 1 维输出。\n",
    "        # Wide 部分：直接使用原始输入特征，保留低阶特征信息。\n",
    "        # 输出层：将 Deep 部分的输出和 Wide 部分的输入拼接后，通过一个全连接层生成最终输出。\n",
    "        # pytorch 需要自行计算输出输出维度\n",
    "        self.output_layer = nn.Linear(30 + input_dim, 1)\n",
    "\n",
    "        # 初始化权重\n",
    "        self.init_weights()\n",
    "\n",
    "    def init_weights(self):\n",
    "        # 初始化权重\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, nn.Linear):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "\n",
    "    def forward(self, x):\n",
    "        # 前向传播\n",
    "        deep_output = self.deep(x)\n",
    "        # print(deep_output.shape)\n",
    "        # concat [batch size, 30] with x [batch size 8]，得到 [batch size, 38]\n",
    "        # 将两个张量（deep_output 和 x）在指定的维度（dim=1）上进行拼接（concatenation）\n",
    "        concat=torch.cat([x,deep_output], dim=1)\n",
    "        logits = self.output_layer(concat) # 输出层，输入维度是 38，输出维度是 1\n",
    "        return logits\n",
    "    \n"
   ],
   "id": "95800ab9c21dfe0e",
   "outputs": [],
   "execution_count": 276
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:24:35.871716Z",
     "start_time": "2025-01-17T11:24:35.865964Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 早停\n",
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        self.patience = patience  # 多少个step没有提升就停止训练\n",
    "        self.min_delta = min_delta  # 最小的提升幅度\n",
    "        self.best_metric = -10000  # 记录的最好的指标\n",
    "        self.counter = 0  # 计数器，记录连续多少个step没有提升\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:  # 如果指标提升了\n",
    "            self.best_metric = metric  # 更新最好的指标\n",
    "            self.counter = 0  # 计数器清零\n",
    "        else:\n",
    "            self.counter += 1  # 计数器加一\n",
    "\n",
    "    @property  # 使用@property装饰器，使得 对象.early_stop可以调用，不需要()\n",
    "    def early_stop(self):\n",
    "        # 如果计数器大于等于patience，则返回True，停止训练\n",
    "        return self.counter >= self.patience"
   ],
   "id": "dafde4c7bc482222",
   "outputs": [],
   "execution_count": 277
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:24:35.884019Z",
     "start_time": "2025-01-17T11:24:35.878719Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "\n",
    "# 评估函数\n",
    "# 因为是回归问题，所以计算准确率无意义\n",
    "@torch.no_grad()  # 装饰器，禁止梯度计算\n",
    "def evaluate(model, data_loader, loss_fct):\n",
    "    loss_list = []\n",
    "    for datas, labels in data_loader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "\n",
    "        # 前向传播\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)  # 验证集损失\n",
    "        # tensor.item() 获取tensor的数值，loss是只有一个元素的tensor\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "    return np.mean(loss_list)  # # 返回验证集平均损失和准确率"
   ],
   "id": "fee145430e7ea5df",
   "outputs": [],
   "execution_count": 278
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:24:35.892677Z",
     "start_time": "2025-01-17T11:24:35.886021Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 训练函数\n",
    "def training(model,\n",
    "             train_loader,\n",
    "             val_loader,\n",
    "             epoch,\n",
    "             loss_fct,\n",
    "             optimizer,\n",
    "             early_stop_callback=None,\n",
    "             eval_step=500,\n",
    "             ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 0  # 全局步数\n",
    "    model.train()  # 训练模式\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "\n",
    "                # 前向传播\n",
    "                logits = model(datas)\n",
    "                loss = loss_fct(logits, labels)  # 训练集损失\n",
    "\n",
    "                # 反向传播\n",
    "                optimizer.zero_grad()  # 梯度清零\n",
    "                loss.backward()  # 反向传播\n",
    "                optimizer.step()  # 优化器更新参数\n",
    "\n",
    "                loss = loss.cpu().item()\n",
    "\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss,\n",
    "                    \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # 评估\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()  # 评估模式\n",
    "                    # 验证集损失和准确率\n",
    "                    val_loss = evaluate(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss,\n",
    "                        \"step\": global_step\n",
    "                    })\n",
    "                    model.train()  # 训练模式\n",
    "\n",
    "                    # 早停 early stopping\n",
    "                    if early_stop_callback is not None:\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        early_stop_callback(-val_loss)\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict  # 早停，返回记录字典 record_dict\n",
    "\n",
    "                # 更新进度条和全局步数\n",
    "                pbar.update(1)  # 更新进度条\n",
    "                global_step += 1  # 全局步数加一\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    return record_dict  # 训练结束，返回记录字典 record_dict\n"
   ],
   "id": "3d36c3eaec6fc763",
   "outputs": [],
   "execution_count": 279
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:25:38.909004Z",
     "start_time": "2025-01-17T11:24:35.895680Z"
    }
   },
   "cell_type": "code",
   "source": [
    "epoch = 20\n",
    "\n",
    "model = WideDeep()\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失,均方误差\n",
    "loss_fct = nn.MSELoss()  # 损失函数\n",
    "# 2. 定义优化器 采用SGD优化器\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.0)\n",
    "\n",
    "# 3. 定义早停\n",
    "early_stop_callback = EarlyStopCallback(patience=10, \n",
    "                                        min_delta=1e-3)\n",
    "\n",
    "model = model.to(device)\n",
    "record_dict = training(model,\n",
    "                       train_loader,\n",
    "                       val_loader,\n",
    "                       epoch,\n",
    "                       loss_fct,\n",
    "                       optimizer,\n",
    "                       early_stop_callback=early_stop_callback,\n",
    "                       eval_step=len(train_loader)\n",
    "                       )"
   ],
   "id": "6fb98b8ab19337eb",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/29040 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "9916ae4503b34222b4161e1adaa65850"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 280
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:25:38.915709Z",
     "start_time": "2025-01-17T11:25:38.910006Z"
    }
   },
   "cell_type": "code",
   "source": "record_dict[\"train\"][-5:]",
   "id": "acc7cd1cd573870",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'loss': 0.21867799758911133, 'step': 29035},\n",
       " {'loss': 0.23680657148361206, 'step': 29036},\n",
       " {'loss': 0.28071436285972595, 'step': 29037},\n",
       " {'loss': 0.245866596698761, 'step': 29038},\n",
       " {'loss': 0.8842835426330566, 'step': 29039}]"
      ]
     },
     "execution_count": 281,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 281
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:25:38.923221Z",
     "start_time": "2025-01-17T11:25:38.917712Z"
    }
   },
   "cell_type": "code",
   "source": "record_dict[\"val\"][-15:]",
   "id": "172382f4d0c2e1a8",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'loss': 0.6311527586595949, 'step': 7260},\n",
       " {'loss': 0.5218695339800652, 'step': 8712},\n",
       " {'loss': 0.5150748966800526, 'step': 10164},\n",
       " {'loss': 0.49547607263873433, 'step': 11616},\n",
       " {'loss': 0.49055670661282197, 'step': 13068},\n",
       " {'loss': 0.4770972936621998, 'step': 14520},\n",
       " {'loss': 0.4705765784433312, 'step': 15972},\n",
       " {'loss': 0.48100049822968394, 'step': 17424},\n",
       " {'loss': 0.45382020002123247, 'step': 18876},\n",
       " {'loss': 0.4474600060527359, 'step': 20328},\n",
       " {'loss': 0.43527835339677234, 'step': 21780},\n",
       " {'loss': 0.4342656471489444, 'step': 23232},\n",
       " {'loss': 0.42191539577416276, 'step': 24684},\n",
       " {'loss': 0.4149798066278319, 'step': 26136},\n",
       " {'loss': 0.40864533121689905, 'step': 27588}]"
      ]
     },
     "execution_count": 282,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 282
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:25:39.043045Z",
     "start_time": "2025-01-17T11:25:38.925224Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "\n",
    "plot_learning_curves(record_dict)  #横坐标是 steps"
   ],
   "id": "e2554260e338697e",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 283
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 测试集",
   "id": "d9f6852bfa6d34fe"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T11:02:34.470310Z",
     "start_time": "2025-01-17T11:02:34.305563Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model.eval()  # 评估模式\n",
    "loss = evaluate(model, test_loader, loss_fct)\n",
    "print(f\"Test loss: {loss:.4f}\")"
   ],
   "id": "506c5db54876b6ed",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test loss: 0.3955\n"
     ]
    }
   ],
   "execution_count": 184
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
